// 13.6 对象移动
/**
 * 新标准的一个最主要的特性是可以移动而非拷贝对象的能力。如我们在13.1.1节（第440页）中所见，很多情况下都会发生对象拷贝。
 * 在其中某些情况下，对象拷贝后就立即被销毁了。在这些情况下，移动而非拷贝对象会大幅度提升性能。
 * 使用移动而不是拷贝的另一个原因源于IO类或unique_ptr这样的类。这些类都包含不能被共享的资源（如指针或IO缓冲）。因此，这些类型的对象不能拷贝但可以移动。
 * 标准库容器、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

class Foo {
public:
// 参数列表后的引用限定符可以是&或&&，分别指出this可以指向一个左值或右值。
// 默认情况下，this的类型是指向类类型非常量版本的常量指针。
// 因为，this是隐式的并且不会出现在参数列表中，所以在哪儿将this声明成指向常量的指针就成为我们必须面对的问题。
// C++语言的做法是允许把const关键字放在成员函数的参数列表之后，此时，紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数（const member function）。
// 至于为何要将成员函数声明为常量成员函数（即const放在成员函数参数列表之后），是因为该函数体内不改变this所指的对象，这样有助于提高函数的灵活性。
// 将const &放在成员函数声明的末尾时，它意味着这个成员函数是作为一个常量对象的成员被调用，并且该函数返回的对象是一个左值引用。
// 换句话说，这个函数承诺不会修改它所属的对象，并且返回的是一个可以用于赋值操作的左值。
    Foo& operator=(const Foo&) &; // 只能向可修改的左值赋值，引用限定符可以是&或&&，分别指出this可以指向一个左值或右值。
    Foo anotherMem() const &;     // 一个函数可以同时用const和引用限定。在此情况下，引用限定符必须跟随在const限定符之后。
    Foo sorted() &&;      // 可用于可改变的右值
    Foo sorted() const &; // 可用于任何类型的Foo。在这个例子中，anotherMem()函数可以被一个常量Foo对象调用，并且返回的结果可以用于赋值或传递给期望接收左值引用的函数。
    // Foo的其他参数
private:
    vector<int> data;
};
Foo& Foo::operator=(const Foo &rhs) &
{
    // 执行将rhs赋予本对象所需的工作
    return *this;
}
// 本对象为右值，因此可以原址排序
Foo Foo::sorted() &&
{
    sort(data.begin(), data.end());
    return *this;
}
// 本对象是const或是一个左值，哪种情况我们都不能对其进行原址排序
Foo Foo::sorted() const & 
{
    Foo ret(*this);                         // 拷贝一个副本
    sort(ret.data.begin(), ret.data.end()); // 排序副本
    return ret;                             // 返回副本
}
// 当我们对一个右值执行sorted时，它可以安全地直接对data成员进行排序。对象是一个右值，意味着没有其他用户，因此我们可以改变对象。
// 当对一个const右值或一个左值执行sorted时，我们不能改变对象，因此就需要在排序前拷贝data。

int main()
{
    // 13.6.3 右值引用和成员函数
    // 除了构造函数和赋值运算符之外，如果一个成员函数同时提供拷贝和移动版本，它也能从中受益。
    // 这种允许移动的成员函数通常使用与拷贝或移动构造函数和赋值运算符相同的参数模式————一个版本接受一个指向const的左值引用，第二个版本接受一个指向非const的右值引用。
    // 定义了push_back的标准库容器提供两个版本：一个版本有一个右值引用参数，而另一个版本有一个const左值引用。
    // 假定X是元素类型，那么这些容器就会定义以下两个push_back版本：
    //void push_back(const X&); // 拷贝，绑定到任意类型的X
    //void push_back(X&&);      // 移动，只能绑定到类型X的可修改的右值
    // 区分移动和拷贝的重载函数通常有一个版本接受一个const T&，而另一个版本接受一个T&&。

    // 右值和左值引用成员函数
    // 通常，我们在一个对象上调用成员函数，而不管该对象是一个左值还是一个右值。例如：
    string s1 = "a value", s2 = "another";
    auto n = (s1 + s2).find('a'); // 该string右值是通过链接两个string而得到
    // 在旧标准中，我们没有办法阻止这种使用方式。为了维持向后兼容性，新标准库类仍然允许向右值赋值。
    s1 + s2 = "wow!"; // 对两个string的链接结果————一个右值，进行了赋值
    // 我们指出this的左值/右值属性的方式与定义const成员函数相同（参见7.1.2节，第231页），即，在参数列表后放置一个引用限定符（reference qualifier）：
    // 引用限定符可以是&或&&，分别指出this可以指向一个左值或右值。
    // 引用限定符只能用于（非static）成员函数，且必须同时出现在函数的声明和定义中。
    // 对于&限定的函数，我们只能将它用于左值；对于&&限定的函数，只能用于右值：
    Foo &retFoo(); // 返回一个引用，retFoo调用是一个左值
    Foo retVal();  // 返回一个值，retVal调用是一个右值
    Foo i, j;      // i和j是左值
    i = j;         // 正确，i是左值
    retFoo() = j;  // 正确，retFoo()返回一个左值
    //retVal() = j;  // 错误，retVal()返回一个右值
    i = retVal();  // 正确，我们可以将一个右值作为赋值操作的右侧运算对象
    // 一个函数可以同时用const和引用限定。在此情况下，引用限定符必须跟随在const限定符之后：
    //Foo someMem() & const;      // 错误，const限定符必须在前
    //Foo anotherMem() const &;   // 正确，const限定符在前

    // 重载和引用函数
    // 就像一个成员函数可以根据是否有const来区分其重载版本一样（参见7.3.2节，第247页），引用限定符也可以区分重载版本。而且，我们可以综合引用限定符和const来区分一个成员函数的重载版本。
    // 当我们定义const成员函数时，可以定义两个版本，唯一的差别是一个版本有const限定而另一个没有。
    // 引用限定的函数则不一样。如果我们定义两个或两个以上具有相同名字和相同参数列表的成员函数，就必须对所有函数都加上引用限定符，或者所有都不加：
    // 如果一个成员函数有引用限定符，则具有相同参数列表的所有版本都必须有引用限定符。

    // 小结
    // 每个类都会控制该类型对象拷贝、移动、赋值以及销毁时发生什么。特殊的成员函数——拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数定义了这些操作。
    // 移动构造函数和移动赋值运算符接受一个（通常是非const的）右值引用；而拷贝版本则接受一个（通常是const的）普通左值引用。
    // 分配了内存或其他资源的类几乎总是需要定义拷贝控制成员来管理分配的资源。如果一个类需要析构函数，则它几乎肯定也需要定义移动和拷贝构造函数及移动和拷贝赋值运算符。

    // 术语表
    // 拷贝并交换（copy and swap）：涉及赋值运算符的技术，首先拷贝右侧运算对象，然后调用swap来交换副本和左侧运算对象。
    // 拷贝构造函数（copy constructor）：当向函数传递对象，或以传值方式从函数返回对象时，会隐式使用拷贝构造函数。如果我们未提供拷贝构造函数，编译器会为我们合成一个。
    // 拷贝初始化（copy initialization）：一种初始化形式，当我们使用=为一个新创建的对象提供初始化器时，会使用拷贝初始化。
    // 如果我们向函数传递对象或以传值方式从函数返回对象，以及初始化一个数组或一个聚合类时，也会使用拷贝初始化。
    // 删除的函数（deleted function）：不能使用的函数。我们在一个函数的声明上指定=delete来删除它。删除的函数的一个常见用途是告诉编译器不要为类合成拷贝和/或移动操作。
    // 析构函数（destructor）：特殊的成员函数，当对象离开作用域或被释放时进行清理工作。编译器会自动销毁每个数据成员。
    // 类类型的成员通过调用其析构函数来销毁；而内置类型或复合类型的成员的销毁则不需要做任何工作。特别是，析构函数不会释放指针成员指向的对象。
    // 左值引用（lvalue reference）：可以绑定到左值的引用。
    // 逐成员拷贝/赋值（memberwise copy/assign）：合成的拷贝与移动构造函数及拷贝与移动赋值运算符的工作方式。
    // 合成的拷贝或移动构造函数依次处理每个非static数据成员，通过从给定对象拷贝或移动对应成员来初始化本对象成员；
    // 拷贝或移动赋值运算符从右侧运算对象中将每个成员拷贝赋值或移动赋值到左侧运算对象中。
    // 内置类型或复合类型的成员直接进行初始化或赋值。类类型的成员通过成员对应的拷贝/移动构造函数或拷贝/移动赋值运算符进行初始化或赋值。
    // move ：用来将一个右值引用绑定到一个左值的标准库函数。调用move隐含地承诺我们将不会再使用移后源对象，除了销毁它或赋予它一个新值之外。
    // 移动赋值运算符（move-assignment operator）：接受一个本类型右值引用参数的赋值运算符版本。
    // 通常，移动赋值运算符将数据从右侧运算对象移动到左侧运算对象。赋值之后，对右侧运算对象执行析构函数必须是安全的。
    // 移动构造函数（move constructor）：一种构造函数，接受一个本类型的右值引用。通常，移动构造函数将数据从其参数移动到新创建的对象中。移动之后，对给定的实参执行析构函数必须是安全的。
    // 移动迭代器（move iterator）：迭代器适配器，它生成的迭代器在解引用时会得到一个右值引用。
    // 引用计数（reference count）：一种程序设计技术，通常用于拷贝控制成员的设计。引用计数记录了有多少对象共享状态。
    // 构造函数（不是拷贝/移动构造函数）将引用计数置为1。每当创建一个新副本时，计数值递增。当一个对象被销毁时，计数值递减。赋值运算符和析构函数检查递减的引用计数是否为0，如果是，它们会销毁对象。
    // 引用限定符（reference qualifier）：用来指出一个非static成员函数可以用于左值或右值的符号。
    // 限定符&和&&应该放在参数列表之后或const限定符之后（如果有的话）。被&限定的函数只能用于左值；被&&限定的函数只能用于右值。
    // 右值引用（rvalue reference）：指向一个将要销毁的对象的引用。
    // 合成析构函数（synthesized destructor）：编译器为未显式定义析构函数的类创建的（合成的）版本。合成析构函数的函数体为空。
    
    return 0;
}